﻿using System;
using System.Collections.Generic;
using Microsoft.Practices.Composite.Events;
using VisualStock.Infrastructure;
using VisualStock.Infrastructure.Interfaces;
using VisualStock.Infrastructure.Models;

namespace VisualStock.Modules.StockMarket.Services
{
    public class DataAnalysisService : IDataAnalysisService
    {
        private IEventAggregator EventAggregator { get; set; }

        private readonly IHistoricalDataService _historicalDataService;

        public DataAnalysisService(IHistoricalDataService historicalDataService, IEventAggregator eventAggregator)
        {
            this.EventAggregator = eventAggregator;
            _historicalDataService = historicalDataService;
        }

        /// <summary>
        /// Calculate the DateTime to RelativeXValue corresponding table
        /// </summary>
        /// <param name="startDate"></param>
        /// <param name="endDate"></param>
        /// <returns>The table which indiciates the date to its corresponding RelativeXValue</returns>
        public static Dictionary<DateTime, int> RelativeXValueCalculation(DateTime startDate, DateTime endDate)
        {
            Dictionary<DateTime, int> tempTable = new Dictionary<DateTime, int>();
            int RelativeXValue = 0;
            for (DateTime date = startDate; date.CompareTo(endDate) <= 0; date = date.AddDays(1))
            {
                // Exclude the Saturday and Sunday
                if (date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday)
                {
                    tempTable.Add(date, RelativeXValue);
                    RelativeXValue++;
                }
            }
            return tempTable;
        }

        #region IDataAnalysisService Members

        public void AnalysisSelector(TechnicalIndicatorType type, string targetSymbol, int value)
        {
            string cacheNameFormat = targetSymbol + "_{0}_" + value.ToString();

            switch (type)
            {
                case TechnicalIndicatorType.DefaultSMA: MovingAverageCalculation(targetSymbol, String.Format(cacheNameFormat, "SMA"), type, value); break;
                case TechnicalIndicatorType.QuickSMA: MovingAverageCalculation(targetSymbol, String.Format(cacheNameFormat, "SMA"), type, value); break;
            }
        }

        /// <summary>
        /// Calculate the moving average of the given stock data, the sourceData must be sorted in a newest first order
        /// </summary>
        /// <param name="targetSymbol">The symbol of the data to be analyzed</param>
        /// <param name="cacheName">The name for data cache purpose</param>
        /// <param name="type">Specify the method to calculate the moving average </param>
        /// <param name="days">The days to be calculated, it must be greater than 5</param>
        /// <returns>The MovingAverage of the given stock data</returns>
        public void MovingAverageCalculation(string targetSymbol, string cacheName, TechnicalIndicatorType type, int days)
        {
            // Exit the method if the specified source data does not exist, or it is a technical analysis data, or days is smailler than 5
            if (targetSymbol.Contains("Tech") || !_historicalDataService.HasHistoricalData(targetSymbol) || days < 1) return;

            // Check if the technical indicator has been cached or not
            if (!_historicalDataService.HasHistoricalData(cacheName))
            {
                StockHistoricalDataCollection dataSource = _historicalDataService.GetHisroricalData(targetSymbol, DateTime.MinValue, DateTime.Today);
                // Return if the amount of source data is insufficient to be analysed.
                if (dataSource.Count <= days) return;
                StockHistoricalDataCollection returnResult = new StockHistoricalDataCollection();
                switch (type)
                {
                    case TechnicalIndicatorType.QuickSMA: returnResult = QuickMovingAverageCalculation(dataSource, days); break;
                    case TechnicalIndicatorType.DefaultSMA: returnResult = DefaultMovingAverageCalculation(dataSource, days); break;
                }
                this.EventAggregator.GetEvent<CacheAnalysisDataEvent>().Publish(new KeyValuePair<string, StockHistoricalDataCollection>(cacheName, returnResult));
            }
        }

        /// <summary>
        /// This method is faster than the DefaultMovingAverageCalculation. However, the result may incorrect if any data is missing.
        /// </summary>
        public StockHistoricalDataCollection QuickMovingAverageCalculation(StockHistoricalDataCollection dataSource, int days)
        {
            // Calculate the first value
            double tempTotalValue = 0;
            for (int i = 1; i <= days; i++)
            {
                tempTotalValue += dataSource[i].ClosingPrice;
            }
            StockHistoricalDataItem firstItem =
                new StockHistoricalDataItem
                {
                    QuoteDate = dataSource[0].QuoteDate,
                    ClosingPrice = tempTotalValue / (double)days
                };

            // Calculate the other values
            StockHistoricalDataCollection result = new StockHistoricalDataCollection();
            result.Add(firstItem);
            for (int j = 1; j < dataSource.Count - days; j++)
            {
                StockHistoricalDataItem item =
                    new StockHistoricalDataItem
                    {
                        QuoteDate = dataSource[j].QuoteDate,
                        ClosingPrice = result[j - 1].ClosingPrice - (dataSource[j].ClosingPrice - dataSource[j + days].ClosingPrice) / (double)days
                    };
                result.Add(item);
            }

            return result;
        }

        #endregion

        /// <summary>
        /// For a large scale data, using this method may cost a long time.
        /// </summary>
        public StockHistoricalDataCollection DefaultMovingAverageCalculation(StockHistoricalDataCollection dataSource, int days)
        {
            StockHistoricalDataCollection result = new StockHistoricalDataCollection();
            int vaildValueCount; // Calculate the number of vaild data, sometimes a few data may be missing
            double tempTotalValue;
            DateTime dt;

            for (int i = 0; i < dataSource.Count - days; i++)
            {
                vaildValueCount = days;
                tempTotalValue = 0;
                dt = dataSource[i].QuoteDate;
                for (int j = i + 1; ; )
                {
                    dt = dt.AddDays(-1); ; // Get the older data 
                    if (dt.DayOfWeek == DayOfWeek.Sunday) dt = dt.AddDays(-2); // Skip the Saturday and Sunday;

                    if (dataSource[j].QuoteDate == dt)
                    {
                        tempTotalValue += dataSource[j].ClosingPrice;
                        j++;
                    }
                    else vaildValueCount--; // Exclude the missing data

                    if (j > vaildValueCount + i) break;
                }
                StockHistoricalDataItem item =
                    new StockHistoricalDataItem
                    {
                        QuoteDate = dataSource[i].QuoteDate,
                        ClosingPrice = tempTotalValue / (double)vaildValueCount
                    };
                result.Add(item);
            }
            return result;
        }
    }
}
